WELCOME TO
"CRACKING LIKE KWAZY"
Kwazy Webbit vs. MaD doG
Author | Kwazy Webbit |
|
Group | DREAD |
|
Subject (AKA 'victim') | PaneKiller 1.24 by MaD doG (any 1.2x should work I think) |
|
Tools required | SoftIce 3.xx |
A text-viewer (notepad will do) |
Win32 API programming reference
(win32.hlp)
If that link is down just find it using any search engine.
You could also ask just about any programmer for it. (on IRC for example) |
Your favorite compiler (only for chapter 3) |
WDasm 8.9x (only for advanced chapters) |
A hex-editor (only for advanced chapters) |
|
Date of writing | May 31st, 1999 |
Hello folks! They call me Kwazy Webbit. I am a new member of the Dutch cracking group DREAD.
In this essay I hope to help you get started with Reverse Engineering, which basically
means that I want to do more than just tell you how to crack this program, but I want to
help you understand how you can crack any program out there. I'm gonna try to take you
through the process of reversing this program one step at a time, unlike A LOT of other
crackers who just write down what they do, and forget to explain WHY they are doing it.
I was a beginner not too long ago, so I've read about a zillion essays, which would be
most of the supposedly good ones. Well, I can tell you, most of them were too highlevel
for newbies, and by far too basic for any advanced cracker. Basically, they were useless
to both sides.
In this essay, I'm assuming you have some minor experience
with SoftIce (If you don't have any, I'll tell you what key to press for everything you
should do).
I suggest you use an 800*600 resolution and a maximized browser to view this text,
so everything will look ok. What would be even better is to print it out, because you won't
be able to read it from the screen when you're using SoftIce.
NOW SETTLE DOWN KIDS, CLASS IS ABOUT TO BEGIN !
HOW IT GETS OUR INFO
Let's start up PaneKiller if it's not already running, and see how we register it.
It has it's own popup menu, and we see an option called "Unlock with registration code".
Sounds good to me. Let's check it out. It has text fields where we should enter our name,
company name and registration code. Ok, time to get cracking :
First thing we noticed was that the program gets our info through regular textfields (like
just about every Win95 program).
"How can we use that to our advantage?", you say?
Well, a program has to call a function to get information out of these text boxes. And since
it is a lot of work to program a procedure for that, Windows 95 has the needed functions ready
to go already, saving the programmer a lot of trouble.
The fact that all programs can use the
same functions is also the great weakness, when it comes to protection schemes.
All programmers know what these functions are, and so do all
crackers (that would be us :).
The most commonly used functions in Windows 95 are:
GetWindowTextA
Definition in the API programmer's reference: The GetWindowTextA function copies the text of the specified window's title bar
(if it has one) into a buffer. If the specified window is a control, the text
of the control is copied. |
GetDlgItemTextA
Definition in the API programmer's reference: The GetDlgItemText function retrieves the title or text associated with a control
in a dialog box. If you're not a programmer, you might be wondering what a dialog
box is. Well, it's basically the same as a normal window, but it works different
internally, which means it requires a different function. |
GetDlgItemInt
Definition in the API programmer's reference: The GetDlgItemInt function translates the text of a specified control in a dialog box
into an integer value. This one's basically the same as GetDlgItemTextA, but this function directly returns a number (a decimal number) without having to translate it from a string. |
Let's try setting these breakpoints in SoftIce: 'bpx GetDlgItemTextA', 'bpx GetWindowTextA',
'bpx GetDlgItemInt' (lots of programs use this one for serial numbers)
and add another if you feel there's an important one missing.
In case you didn't know already: 'setting a breakpoint' means that from now on, every time the
specified function is used (called) softice stops everything that's going on and pops up on your screen.
Let's enter our info (name and company), and a bogus
serial number. We press the 'OK' button, and SoftIce pops up. (kewl, one of our breakpoints
worked. That's a good start!) We look in the SoftIce windows and see that GetDlgItemTextA is
the function that was called. We are now one instruction after when the function was called.
That means, we're now INSIDE the function GetDlgItemTextA. We can press F11 to get
back out of the function, and back into the code of our program.
There we see the call the
program made to GetDlgItemTextA, on instruction before our current position.
First, let's look up what parameters that function uses in
our Win32 API reference.
The reference says GetDlgItemTextA uses four parameters:
hDlg | The handle to the dialog box.
Basically it's the dialog box version of hWnd, which is the handle to a program window,
used by many (if not most) windows functions.
(Not much use to us right now) |
|
nIDDlgItem | The ID of the 'control' where it should get the text from. A control is
just a part of the Dialog box, like a text field or a button, and they are referenced by
their unique control ID.
(Not much use to us right now) |
|
lpString | The memory address where the function should put the string it gets.
Now this is very important! This way we can see where the program puts
our name, company name and serial. |
|
nMaxCount | The maximum number of characters the function will get.
(Not much use to us right now) |
Windows uses the method of PUSH-ing things onto the stack when it calls a function.
Something very important to remember is that in assembly language, the last parameter gets
PUSH-ed first. I'm not going to explain why, coz it would take too long. If you must know,
I suggest you learn ASM.
So what you are really seeing in ASM is this:
PUSH nMaxCount
PUSH lpString
PUSH nIDDlgItem
PUSH hDlg
One of the signs of this method of using the stack is the constant tampering with the ESP and
EBP register at the beginning and end of a function. Also, there will always be some more
right after the function call. All this is totally unimportant to us reversers, and can be
ignored.
We see two more calls to GetDlgItemTextA, and there should be coz there are three info fields.
This means SoftIce will pop up two more times for the other calls. Just step over them, and
F11 out of 'em like you did for the first one. Now you should be just after the calls, and
(what will probably prove to be) the beginning of the protection. The program has just
finished getting the info you entered, so let's check now where he put it.
let's see what address is PUSH-ed to the GetDlgItemTextA functions as lpString:
CALL 1 : [EBP-50]
CALL 2 : [EBP-40]
CALL 3 : [EBP-D0]
Don't let them fool you, the order in which the code gets input doesn't have to be the same
order as the textfields on the window. Let's check which address has which value.
To do that we type 'd ebp-50', 'd ebp-40' and 'd ebp-D0'.
You'll discover the following:
[EBP-50] | = | serial number (also referred to by me as s/n or reg code) |
[EBP-40] | = | name |
[EBP-D0] | = | company name |
That's good to know!
THE PROTECTION
Let's try and get a general idea about how the protection works before getting into the
exact details. Lots of crackers don't do this, and will often get lost in a lot of code,
that will afterwards prove to be totally irrelevant.
Now, we're just after the program got our info. Here we see a function being called.
It's an internal function (that simply means that its not an API call), so we don't know
(yet) exactly what it does. We can still check out the parameters its getting just by
looking what was PUSH-ed just before the call.
There is only one variable being PUSH-ed, [EBP-50] which is our reg code.
hmmm... well, it can't be the registration check, coz that would (probably) need
our name / company name and neither is PUSH-ed here. Let's just skip this function for now.
Next is another function call. Its parameters are our s/n, our name, our company name,
and one other variable, [EBP+08]
I'm naturally curious, and immediately want to know what that variable is.
Curiosity is in my humble opinion the most important demand for becoming a good cracker.
Because if you don't check out everything and want to rush things, you'll usually fail.
It's important to try to understand what's going on. If you can do that, you can take on
any protection scheme.
Well, that's all fine.. but how do we find out what [EBP+08] is? Ok, first we'll check
what's at EBP-08 ('d ebp-8') hmmm... Well it obviously isn't a string. so it's probably
a number. We can see that it is a DWORD value (which is 4 bytes = 32 bits), because
[EBP+08] is being put into ECX before being PUSH-ed, and ECX is a 32-bit register.
You'll see a hexadecimal figure there. In my case it said 'FC070000'
Variables are put in memory in reverse, one byte at a time. Confused? Well, let me help
you. One byte is two nibbles. (nibbles are the ones that go from 0-F and are used for
hexadecimal numbers). In other words, you have to take those numbers in the data window
two nibbles (one byte) at a time. In my case : 'FC 07 00 00'.
Is it becoming a bit clearer? good.
They are put into memory in reverse, so the real number would be '00 00 07 FC'.
Hmmm, now how much is that? Let's ask SoftIce : '? 7FC' (use your figure instead of mine)
SoftIce will give you the number in hex, decimal and ASCII. the number still tells us nothing!
Damnit!
Well, there is one more thing we can do: check if the variable has been used before.
Maybe that can help us. YES! It's been used in every one of the GetDlgItemTextA calls.
It's always the last variable to get PUSH-ed, so that makes it ...(drum roll)... the hDlg!
In this case it proved to be unimportant for the cracking of this program, but it's always a
good idea to check these 'mystery variables' out, even if it's just to help you to better
understand the program.
Alright, now we understand ALL of the parameters given to the function: Our name, company,
reg code and the window handle. Wow, this looks like an important function, requiring all that
info! This will probably turn out to be the function where the core of the protection scheme is
set up. However, first we'll look a bit more ahead. Remember, we're only trying to get a good
general idea at this point. We'll look into the function(s) later.
After the function call (and cleanup) there is a TEST being done, to check if EAX is zero or not.
Then there is a JZ instruction, in other words : if EAX is zero, he jumps to the given address,
otherwise he keeps on going where he is. Simple logic should make you realize that de value
of EAX will probably be the result of the registration check. (GOOD/BAD)
Let's look at what the different bits of code actually do, so we can check whether that
assumption was correct or not.
Let's assume for now EAX was 0 => TEST EAX,EAX => the zero flag is set => the JZ ('jump if zero'-
instruction) decides to jump to 403102 => once there he immediately starts to prepare for a call
of the function MessageBoxA. Let's look it up in our API reference. We find it has the following
parameters:
hWnd | => | The window handle (see earlier in this essay for more details) |
lpText | => | The address of the text to be displayed in the messagebox |
lpCaption | => | The address of the text to be displayed in the title bar of the messagebox |
uType | => | The style of the messagebox |
(remember the parameters are being PUSH-ed in reverse order)
Let's take a look at the text being displayed in the messagebox. That should give us a good
idea of why it's being displayed. Let's see, the third variable being PUSH-ed was 00422194
so that should be the address of the text. We do a 'd 00422194', and look at the memory window.
We see: "The registration code you have entered is incorrect, Please try again."
Hmmm, so this
is a messagebox for when you've entered the wrong code.
Now, for the other possibility:
EAX was not 0 => TEST EAX,EAX => the zero flag is cleared => the JZ decides not to jump =>
first he calls some function with as parameters our name, company and s/n. After that, he
shows us a MessageBox.
Let's see what this one would say: 'd 00422188'. It says: "Thank You!".
That sounds like something it would say when you enter a correct s/n.
Apparently, EAX should not be zero when we reach the TEST EAX,EAX instruction.
Okay, that's all for the first global reconnaissance (I love army language :).
Let's take a look at what we know the progarm does so far:
Call GetDlgItemTextA (3x) | To get our code, name and company. (In that order) |
Call Function1 | Still unidentified, does something with our serial number,
but is an unlikely candidate for being the protection scheme. |
Call Function2 | This one uses our name, company, AND our serial.
It seems to be the best guess to find the protection. |
Test to see if Function2 returned zero. | Yes => | Show
MessageBox: "The registration code you have entered is incorrect, Please try again." |
No => | Do something with our name/company/serial, then show MessageBox: "Thank You!". |
CRACKING IT
There are three ways to crack this program (and most other programs):
1- The BAD way: | using SoftIce to force the program to accept our bad serial number as if it was correct, AKA 'patching'. |
2- The GOOD way: | determining/calculating the right s/n for your name. |
3- The PROFESSIONAL way: | making a 'key generator'. If you don't know how to code, you can skip this method.
I will also go over two imho awesome variations on this method, both for assembly programmers
ONLY:
- The DREAD way
- Kwazy Webbit style
|
I'll go over all three methods here.
1- The BAD way
At the test EAX should not be zero, so some you would think some easy ways to patch this program would be:
- | just before the TEST-instruction we could change thevalue of EAX to a non-zero value.
(Here, I found out something interesting: EAX can only be 00000001 (good s/n) or 00000000 (wrong s/n), otherwise Windows WILL
crash!! There was no way to know this without trying (well, not without studying all the code first), so just consider me your
fluffy big-eared guinea pig :) |
|
- | right after the TEST we could turn off the Zero flag, so we still won't jump even though EAX was
zero at the TEST.
(In softice, use the mouse to click on the Z on the top-right of the window,
it should now be highlighted. Press Insert to toggle it off or on.) |
|
- | we could change the JZ 00403102 into a JNZ 00403102 (jump if NOT zero) which would mean that
EAX should now be zero at the TEST. Now that won't be too hard to do, will it? :) |
I have tested all of these, and I can truly say none of
them work, because this program checks if the s/n is correct every time it starts.
We can of course try to find that part of the program and patch the program permanently
to make sure we can still crack it, but why shouldn't we simply get the right serial, and
enter that? It'll probably be just as much (or maybe even less) work, AND without the risk
of having damaged a part of your program, that you'll need later on. So, instead let's try
to do it:
2- The GOOD way
Now we're going to look at how the program checks whether you entered the right serial.
(also known as the 'protection scheme'). To do that, we're going to look at the two functions.
The first function:
Let's single-step (F10) through the code until we reach the call to the first function.
We'll take a look inside it (F8). We see that the first character (byte) of our s/n is put into
ECX. Then, it checks whether it's a NULL sign (00 in nibbles). Null signs are used to indicate
the end of a normal string (it's VERY important you know that!). That's why they call them
zero-terminated strings. Then it checks if it's a space, a '-' or a '.' Let's assume for a
moment that those characters are not required. This could save us a lot of trouble trying
to figure out what it does exactly when each character is encountered.
(Let's not take that curiosity thing too far now, we don't want to be here for days doing
something that will prove to be useless :) After all, if we are proven wrong, we can always
go back and still find out what to do.
When a normal number was entered as s/n, the function does nothing. It just checks each character
for the ' ', '-' and '.' and moves on to the next when done. Apparently, this function just
checks for strange characters in our reg code.
Lets step out of this function (F11) and move on towards the second function (using F10).
The second function:
This one seemed earlier like it would be the protection scheme, and it is now pretty much
the only possibility left, so it appears we were right. Let's step inside.(F8)
First we see that a function is being called with three parameters. Let's see what they are.
the first is an address put into EAX. When we dump at that address ('d eax') we see what looks
like another address (in reverse order, explained earlier). when we see what's at that address,
there's nothing we can recognize there. Hmm, strange. But let's move on for now. The next
parameter being PUSH-ed is 0422078h. Now that looks like an address too. let's dump that one.
Again, we are left with something unknown. I don't like this one bit, but let's keep moving for
now, we might find out later on what it is. The third and last variable PUSH-ed is also an
address. Now, let's see if this one can be identified. After the code puts the address in ECX,
type 'd ecx' to see what it points to. AHA! Now that seems familiar. It's our reg code.
So, the only known (to us) parameter it gets is our serial number. As mentioned before, the
correct reg code is probably dependent on the name and/or company name you enter, so this is not
likely to be the protection scheme. So what is it? What does do to our s/n? Calm down, and let's
try and figure that out. A quick look inside the function (F8) doesn't tell us much, because
another function is also being called, making things difficult. Let's step out of the function,
and see what would be next. Right after the function call, we see a call to MessageBoxA.
Let's see what it would say: 'd 42207C'. The data window says: "The registration code is not
in the correct format". OK! So, it is probably safe to assume now, that the function just
checks whether the reg code is in the correct format or not, which probably means without
and weird characters or something like that. Okay, our code is in the correct format, so we just
jump past the messagebox, and move on, deeper into the protection..
Next, a function is called with our name as parameter. If we look ahead, we see that the exact
same function is being called for our 'company name' as its parameter. After each call, EAX is
stored into memory. But wait, what's EAX?! Well, after a call to a function, EAX always contains
the return value. Programmers (should) know exactly what I mean by that. The rest of you
can just take it from me that functions pretty much always return a value, and that that value
is pretty much always put in EAX before returning. Knowing that, we can see that the return
values of the two calls to that function are stored in [EBP-08] and [EBP-0C] respectively.
Let's step over the functions, and see what's in those two addresses. Hmmm, two numbers that
mean nothing to me. But they are probably important, because they are used again right after.
Now comes the most important part of the protection:
(1) | MOV EAX, [EBP-04] |
(2) | XOR EAX, [EBP-08] |
(3) | CMP EAX, 047D6D93 |
(4) | JNZ 00402E74 |
(5) | MOV EAX, 00000001 |
(6) | JMP 00402E74 |
- (1) | Hey, what's in [EBP-04]? Let's step over the first line (F10) and look at what was put
into EAX. Ok, that's our serial number. Now comes the test to see if you are truly paying
attention, there's something strange going on with our serial number!
'no way, it looks ok over here..' I hear you saying. True, but that's the strange part!
It shouldn't be looking normal. It's not in decimal or ASCII display. It's in hexadecimal
notation. It shouldn't look like your serial at all. Apparently, PaneKiller assumes
the serial code is a hexadecimal number, instead of a decimal. In other words, we can
use a hexadecimal number as a reg code, so we can use 0-F instead of 'just' 0-9.
Very useful info.. If you didn't notice this right away, don't worry. Neither did I. :)
Anyway, EAX now contains our reg code. |
- (2) | In this line our reg code is XOR-ed with the number that was derived from our name
(remember, from that function earlier?). In case you don't know what XOR is, I'll
give you a very short explanation. XOR is short for 'eXclusive OR'. What it does:
It takes one bit from each variable (that means it now has two bits)
It returns 1 if they are different (one of them is 0 and the other is 1)
It returns 0 if they are the same (both are 0, or both are 1)
It puts the result in the appropriate position in variable 1
It goes to the next bit and starts all over again
Maybe this 'truth table' will help clear it up a bit: |
XOR Truth-table
| Got it? I hope so.
So, in this case the result is stored in EAX (which is variable 1) |
|
- (3) | It now checks if the result of the XOR is 047D6D93 h. |
- (4) | If it isn't, it jumps and we are now at another attempt (a second chance) to see if it
will get the correct result by using our company name in line (2) and a different number
in line (3). When you fail that one it jumps to a line where EAX is XOR-ed with itself.
(all bits in EAX are of course the same as in EAX, so all bits will be set to zero. It
is basically the same as MOV EAX, 0) Then the function returns with EAX being zero.
As we saw earlier, that means: wrong serial. We don't want that.
If it is, the jump is not made, and |
- (5) | MOV EAX, 00000001 is executed. In other words: EAX is now 1 (=NON-ZERO!) |
- (6) | Then it still jumps to the end of the function, this time skipping the XOR EAX, EAX
instruction, and returning with EAX not zero. YES! Our registration is successful. |
The beauty of the XOR instruction is that it is so easy to reverse.
What I'm saying is that after:
A=B XOR C (A,B and C being variables)
and you have two of the three variables, you can get the third by
XOR-ing the two you have. So, after the previous XOR instruction,
B=A XOR C
C=A XOR B
REALLY?! sure, just check with the truth table above. Draw your own conclusions.
So, how do we use that here? Well, you know that after the XOR instruction, EAX should be
047D6D93 h (in the above example that would be A, because the result is stored in variable1,
remember?) Also, we know the code that was derived from our name.('d ebp-8', and ALWAYS
remember, that numbers are stored in reverse order in memory. In my case, in memory it was
EB A6 D5 D2 (derived from the name 'Kwazy Webbit'), so my 'name-code' would be D2D5A6EB h)
Let's call that the B in our example. now, we can use the C=A XOR B to get C.
C would then be [EBP-04], our serial number! KEWL!
How do I do that? Well, there are several ways, I'm sure there are special calculators
available on the web, but I don't have one personally. You could also make your own, if you are
a programmer (remember that you'll need hexadecimal numbers for this one).
I always do these operations on a simple piece of paper. First you write down the binary number
for the two hexadecimal numbers. (not as hard as it seems, you can simply translate them one
nibble at a time, using this table:
Hexadecimal | Binary |
0 | 0000 |
1 | 0001 |
2 | 0010 |
3 | 0011 |
4 | 0100 |
5 | 0101 |
6 | 0110 |
7 | 0111 |
8 | 1000 |
9 | 1001 |
A | 1010 |
B | 1011 |
C | 1100 |
D | 1101 |
E | 1110 |
F | 1111 |
Then just use the XOR truth-table to do the rest.
to convert the result back to hex, just use the hex/binary table again.
I'm sure a calculator would be faster though, but I haven't really searched for one yet.
Example:
D2D5A6EB h was the name-code for Kwazy Webbit
My serial number should be: 047D6D93 XOR D2D5A6EB
047D6D93 | => | 0000 | 0100 | 0111 | 1101 | 0110 | 1101 | 1001 | 0011 | |
D2D5A6EB | => | 1101 | 0010 | 1101 | 0101 | 1010 | 0110 | 1110 | 1011 | |
| | ________________________________________________ | XOR |
| | | | | | | | |
Serial Number | => | 1101 | 0110 | 1010 | 1000 | 1100 | 1011 | 0111 | 1000 | |
Serial Number = D6A8CB78 h
Since we discovered the s/n is a hexadecimal number, we can simply leave it this way. We don't
have to convert it to decimal like most other serials. Just try to enter 'Kwazy Webbit' as name
and enter this s/n (or if you've already made one, by all means, use your own!) It should give
you the messagebox we all knew was coming: "Thank You!"
The next time you start up PaneKiller, it will be the registered version. And we managed to do
it the way it was meant to be done, without brutally forcing the program into accepting our bad
serial, but by getting the right one instead.
3- The PROFESSIONAL way
As mentioned before we'll make a 'keygen' here. That's short for key generator, a program
that will let you enter your name and/or company, and will give you the matching serial number.
To make this, you obviously have to know how to program. I'm assuming you know how to do that,
from this moment on. Now, I'll leave the coding of the rest of the progam over to you (The way
the window looks, making a WndProc, etc.), just make sure you have a window with two text fields
and a button.
The first textfield is for you to enter your name.
The second textfield is for the program to put the calculated reg code.
The button is to start the calculation.
Schematically the program would do this:
1- | Button pressed |
2- | Get entered name from textfield1 and put it in memory (a variable) |
3- | Use the name to calculate the matching name-code (just like Painkiller does) and store that into a variable |
4- | Name-code XOR 047D6D93 (remember?), store the result (that's the serial number) |
5- | Translate the serial number to the matching string |
6- | Put that string in textfield2 |
The most important part is obviously the translation from the name to the name-code.
Something like this might be difficult, but when we think about it for a second, it's easy:
We should just do the same thing as PaneKiller uses for name to name-string translation.
Let's look up where that was again. The function we want to mimic is located at 04031AAh.
Now take a look inside (F8). Alright, there are no other functions being called and there
are no jumps being made to a place outside this function. (04013AA to the RET-instruction)
That will keeps things nice and simple. We can just copy the code inside the function, and
put it into our own (sorry, no copy&paste in SoftIce ;). 'But wait! That's not in my
programming language', you say? Alright, I program in
assembly myself, but I've been told
it can also be done very easily in C/C++ by putting the code in an
'asm{ ... }'-block. For other languages, I suggest you check your help file for info.
Now there's one more problem with that code: the [EBP-08], [EBP-04], etc.
Those are just local variables. But what kind? Well, you can see that [EBP-04] and [EBP-08] are
DWORD variables, because of the instructions at the beginning of the function:
MOV DWORD PTR[EBP-04], 00000000
MOV DWORD PTR[EBP-08], 00000000
The uncompiled version would look something like this:
MOV DwordVar1,0
MOV DwordVar2,0
Got it?
Then there's another 'variable' being used: [EBP+08]
(remember, this is a new function so this is NOT the same variable
as hDlg, even though they look the same). When we dump this variable ('d ebp+8'),
we see what looks like an address. (Stored in reversed bytes, remember that.)
In my case, and if I'm not mistaken, it should be the same for you, it said 'D0 F6 67 00'.
When we dump that address ('d 67F6D0'), we see it is our name. So, that variable holds the
address to the start of our name. In C/C++ this would be a pointer to the beginning of the
array of char's if I'm not mistaken. Of course! It's the parameter that was PUSH-ed before
calling this function! I suggest you start writing down all the code in this function, except
of course for al the 'cleanup work' with EBP and ESP at the start and the end of the function.
Just use any names you like for the local variables. (I know, I know... There are a lot of
crackers wondering why the HELL I'm telling you to write down the code instead of decompiling
the program, and copy&pasting it. Well, the reason is that I wanted to keep the 'Required tools'
section down to an absolute minimum, also i'm hoping you'll start to think a bit about what
the code is doing.)
For getting the name parameter to the function, you can (I think) just use the syntax that
matches your programming language.(Again, you might wanna check your reference on that)
If you did everyhting right, you should now have a function you could call NameToNameCode
which uses one parameter(the address to our name), and returns a DWORD value: our namecode.
Then you XOR that value with 047D6D93h, giving you the serial number!
Now there's just one final problem: how do you show it to the user? You'll need to convert it
to a string first. Of course we could write our own function (I did), but there is an easier
way to do this. There is an API function specially designed for this kind of thing: wsprintf.
If you check you API reference, you'll see that it can convert numbers to strings, etc.
The explanation you'll find there is not very good(I didn't get it at first), so I'll give
a (hopefully) easier explanation here. When you want to print a variable into a string, It
will need to be converted to characters. There are several ways for that, depending on what the
variable is: a single character, another string, a decimal number, and a hexadecimal number.
To show the function where inside the string you want it, you'll need to put a symbol there.
wsprintf uses the '%' token, plus a character indicating what the variable is.
example (in C/C++):
int Age = 50;
char Question = '?';
char[50] buffer;
wsprintf(&buffer,"Wow, you are %d already%c",Age,char);
After this call, buffer would be "Wow, you are 50 already?"
In assembly (masm) this would be:
FormatString db "Wow, you are %d already%c",0 ;Remember, strings are zero-terminated..
buffer db 50 dup(0) ; This creates an array of 50 bytes all initialized at 0
Age dd 50
Question db "?"
push char ;Remember, pushing in reversed order
push Age
lea edx, FormatString
push edx
lea edx, buffer
push edx
call wsprintf
(note that you can't use the MASM 'invoke' syntax here, because wsprintf has an unknown number
of parameters.)
Now we just need to convert a hexadecimal number to a string, we dont need anything else there.
Looking in the reference, we see that for an uppercase hexadecimal number you need to use "%X".
So, that's going to be our entire FormatString. Also, the number we want to use, is returned in
EAX, so we can just use EAX as our parameter when using assembly:
FormatString db "%X"
buffer db 50 dup(0)
.... ;The call to NameToNameCode
push eax
lea edx, FormatString
push edx
lea edx, buffer
push edx
call wsprintf
Not too bad, eh?
In C/C++ the same can be achieved by doing:
char[50] buffer;
wsprintf(&buffer,"%X",NameToNameCode(&name));
All we have to do now, is put the buffer into textfield2, so the user can see it too.
In masm:
invoke SetWindowTextA, hWnd, ADDR buffer
In C/C++:
SetWindowTextA(hWnd,&buffer);
Et voila! We have just made a key generator. Just compile it, and test it.
The DREAD way
This is a variation on 'The PROFESSIONAL way'.
It is hard but also very cool. I call it the DREAD way, because it is the
preferred method of several DREAD members.We are going to take PaneKiller, and build the
keygenerator into the program itself. The source code can only be decompiled as assembly, and
that is why I said earlier that if you don't know assembly, you can forget about cracking the
DREAD way.
We are going to add code to the program, so we will need some space to put it in. There is
never any room left in the program (why would there be?), so with most programs you will have
to append your own code to the end and then fiddle around with internals of the parent program
(see Razzia's essay on making his razziapad for more info). In our case we do not have to go
through this (difficult) process, because we can 'make' some space inside PaneKiller.
You may be wondering where, beacuse there doesn't seem to be an obvious space avaiable.
However, after thinking about how to do this, I remembered something. We've always assumed
it uses our name to calculate our serial number. But as we saw earlier, if we fail that test
it checks to see if the code is correct for our company name. Aha! Now, if we will just assume
the serial number 'matches' our name (just like we've done so far), we do not need this code.
We can just take it out, and put our keygenerator in it's place. A problem arises: that is only
a very tiny bit of code, how are we supposed to fit a whole keygenerator in there? Well, the
answer is simple, just use the equipment available. Use what the coders have given you.
As we saw, the program will do the following when you enter a wrong serial:
(1)- It calculates our 'name-code'
(2)- It calculates our 'company-code'
(3)- It checks whether the code is right for the entered name and fails.
(4)- It checks whether the code is right for the entered company name and fails.
(5)- It returns with EAX=0
(6)- It displays a messagebox saying we have the wrong serial number
As you may already have noticed, the code in (2) is no longer necessary, because we're not
going to perform (4) anymore. This will give us some more space to work with.
Let's take a look at the code:
00402E42 | mov | ecx, dword ptr [ebp+0C] | ;Put the address of our name in ecx |
00402E45 | push | ecx | ;Push ecx for use in function |
00402E46 | call | 004031AA | ;Call function to get name-code |
00402E4B | add | esp,00000004 | ;Restore the stack after the function call |
00402E4E | mov | dword ptr [ebp-08],eax | ;Store name-code in memory |
|
00402E51 | mov | edx, dword ptr [ebp+10] | ;Put the address of our company in edx |
00402E54 | push | edx | ;Push edx for use in function |
00402E55 | call | 004031AA | ;Call function to get company-code |
00402E5A | add | esp,00000004 | ;Restore the stack after the function call |
00402E5D | mov | dword ptr [ebp-0C],eax | ;Store company-code in memory |
See? We can take out that entire second half! That is 00402E60-00402E51 = 0Fh or 15 bytes.
The next code consists of the two checks to see if the serial number was correct:
00402E60 | mov | eax, dword ptr [ebp-04] | ;Put serial number in eax |
00402E63 | xor | eax, dword ptr [ebp-08] | ;XOR s/n with name-code |
00402E66 | cmp | eax, 047D6D93 | ;Compare result with 047D6D93h |
00402E6B | jne | 00402E74 | ;If it is not the same, check if it 'matches' the company-code |
00402E6D | mov | eax, 00000001 | ;Otherwise, put 1 into eax, causing the program to be registered |
00402E72 | jmp | 00402E8B | ;Jump to the end of the function, return with eax=1 |
|
00402E74 | mov | ecx, dword ptr [ebp-04] | ;Put serial number in eax |
00402E77 | xor | ecx, dword ptr [ebp-0C] | ;XOR s/n with company-code |
00402E7A | cmp | ecx, 0177F29F | ;Compare result with 0177F29F |
00402E80 | jne | 00402E89 | ;If it is not the same jump to 00402E89 |
00402E82 | mov | eax, 00000001 | ;Otherwise, put 1 into eax, causing the program to be registered |
00402E87 | jmp | 00402E8B | ;Jump to the end of the function, return with eax=1 |
00402E89 | xor | eax, eax | ;Zero eax, causing the program to remain unregistered |
00402E8B - 004202E8E | Cleanup stuff and return from function |
In this code, we can skip the bit from 00402E74 up to -but not including- 00402E89 . We cannot
skip the instruction at 00402E89 because the program needs either 0 or 1 in eax to
keep from crashing.
Anyway, this will give us another:
00402E89-00402E74 = 15h or 21 bytes of space.
This brings the total to 15 + 21 = 36 bytes of space for us to use. Any highlevel language
programmer would probably see this amount as something not even worth mentioning, but for any
decent assembly-coder, this is quite enough. (You'll see later on, we'll have room to spare :)
Let's start coding...
Well, I assume you've read the part about how to make a keygen, because I'm not going to explain
the entire procedure again. I mentioned earlier that the trick to making this kind of built-in
keygen in a minimal amount of space, is to use what the program gives to you to the max.
Let's make that a little more concrete: In our keygen, the most difficult part of the 'real'
code was where we reproduced the code the program used to calculate the name-code from the name.
Well, we don't have to do that AT ALL here. It's already been calculated by the program, and
stored in memory at [ebp-08]. Well, that saves us a lot of coding. Another part of our keygen,
was the use of wsprintf to convert the hexadecimal number to a string, so we could see it as
a user. If you use WDasm to decompile PaneKiller and look in the imported API functions you
will see it already imported the function wsprintfA (it's the last one in the list). All we
have to do is use it... KEWL! They've made it too easy! :]
By the way, in case you were wondering: wsprintf is the same as wsprintfA.
So, that should be easy enough to use in our code. What else do we need? Oh, of course..
We need some way to show (the string version of) our serial number to the user. Oi, sounds
like a problem. Well, it isn't! We already know the user is going to see a messagebox saying
he had the wrong serial. Why shouldn't we just overwrite that text with our calculated serial
number? Then the messagebox would show the REAL serial number if you enter a wrong one..
Now, let's get coding already...
Before we start coding, there's one more problem we need to address: there's a part of the
original program (00402E60-00402E72) between our two code snippets. This means that we cannot store things in
the registers used in that part. In this case, it's not a big problem. The code only uses eax.
Well, I think we can avoid using eax for storage between the two sections, don't you? :)
By this I also mean the code snippet doesn't use the stack at all (no PUSH or POP or any other
messing around with ebp or esp), so we can go ahead and use the stack too.
How much more am I going to have to read through to get to the coding part?!
Well, here we start coding.
Oh.
We know the name-code is stored in [ebp-08], so let's put that in eax:
mov eax, dword ptr [ebp-08]
Now we do the same thing we did in our keygen, we XOR it with 047D6D93h :
xor eax, 047D6D93h
We have the valid serial number in eax now, we just have to convert it to a string:
push eax
lea eax, [ebp-0C]
push eax
mov [ebp-0C], 00005825
push 00402194
call wsprintfA
NOTE: you CAN stilluse eax for anything but storage, because the original program doesnt
store anything in it either (You can see that because of the instruction at 00402E60).
The second line might call for some further explanation: the DWORD variable at [ebp-0C]
used to be the 'company-code', so it is no longer used. We can used it for our own purposes,
by storing a DWORD there. But what is 00005825? Well, first of all, it's going to be put in
memory backwards: 25 58 00 00
this is a zero-terminated string: '%X',0,0
You may recognize this from the Keygen tutorial, as the format string to convert hexadecimal
numbers to strings. We cannot PUSH it directly (ie PUSH 00005825), because the wsprintf
function requires the address to the format string, not the string itself. So, we do
lea eax, [ebp-0C]
to get the address of the string into eax, and then we
push eax
to give it to the function.
The last variable to be PUSH-ed (the first in the API description) is the destination for the
string. As we said before, we'll print it over the 'invalid code' message. To get the address
to that variable, we look (in WDasm) at the "Data Hex". We search for the message:
"The registration code you have entered is incorrect. Please try again."
You'll find it at 00402194, so we push that to wsprintfA.
Then, we call the function wsprintfA (after all its parameters are PUSH-ed) and just let
the original code go on. We should see the messagebox that previously said our code was invalid,
and now it shows us the real code. But we're not that far yet. We have to change the code first.
We don't have the source code, so we can't just enter our code and compile it. We're going to
to use a hex editor instead. To do that, we'll have to replace the hexadecimal representation
of the old code with the hexadecimal representation of the new (wow, tough words).
We can find the old one pretty easily, it's next to the disassembled instruction.
just write down the code for the parts we're gonna replace.
For the first part you should get:
8B 55 10 52 E8 50 03 00 00 83 C4 04 89 45 F4
(this is the code representing the instructions)
The second part should be:
8B 4D FC 33 4D F4 81 F9 9F F2 77 01 75 07 B8 01 00 00 00 EB 02
But how do we translate our assembly code to something like that?
With normal programs, it's simple. You just use a compiler for that. But we don't have a full
program. We just want to replace a small part of it. To get the hexcodes for our code, we
can use programs like HIEW(press F3 and then TAB in decode mode) and SOFTICE (press'a') by
'assembling' a new code into the program you have loaded. This change doesn't have to be saved
to disk. You can just enter your assembly code, and the HIEW/SI will show the hexcodes in the
main window, next to your code. But this way is fast rather than educational. You might find it
more interesting to do this manually, like me. The way I figured out what the appropriate codes
were, was by looking at the codes for similar instructions in the code of PaneKiller
(decompiled by WDasm), and looked at what the code was that they used. Allow me to demonstrate:
I have
mov eax, dword ptr[ebp-08]
When i start looking at the source of PaneKiller (I start at a random place), I quickly found
these two instructions:
:0040E6EC 8B45A0 mov eax, dword ptr [ebp-60]
:0040E6FA 8B4590 mov eax, dword ptr [ebp-70]
Now, start thinking about how that code might work. I'll tell ya: '8B45xx' is used for every
'mov eax, dword ptr [ebp+xx]' instruction. In our case, xx is negative (-60 and -70). When
the counter goes below zero, it starts over at FF.
It's easier to explain by illustrating:
8B45FF mov eax, dword ptr [ebp-1]
8B4500 mov eax, dword ptr [ebp]
8B4501 mov eax, dword ptr [ebp+1]
Knowing that, we can figure out that the code for:
mov eax, dword ptr [ebp-08]
should be:
8B45F8
Just do something similar with the other commands, and you'll have the whole list of codes:
8B45F8 ( = mov eax, dword ptr [ebp-08] )
35936D7D04 ( = xor eax, 047D6D93 )
50 ( = push eax )
8D45F4 ( = lea eax, [ebp-0c] )
50 ( = push eax )
C745F425580000 ( = mov [ebp-0c], 00005825 )
6894214200 ( = push 00422194 )
FF153CE24100 ( = call dword ptr [0041e23c] )
Make sure you still remember which code represents which instruction!
The reason for that is the block of original code in the middle of our own.
We can't cut a single instruction in half, so the cut will have to be made between two
instructions. Remember, we have 15 bytes in the first part, and 21 in the second.
I made the cut between the second
push eax
and
mov [ebp-0c], 00005825
Which gives me:
8B45F835936D7D04508D45F450 (13 bytes)
for the first part, and
C745F4255800006894214200FF153CE24100 (18 bytes)
for the second part.
You may notice you will have some space left over now (15-13=2 in the first part, and 21-18=3
in the second), this is not a big problem.. You can just NOP them out. NOP instructions do
absolutely nothing, except take up 1 byte of space. The code for NOP is 90. This makes:
8B45F835936D7D04508D45F4509090 (15 bytes)
for the first part, and
C745F4255800006894214200FF153CE24100909090 (21 bytes)
for the second part.
That should do it. We now know what code we want to replace, and we know what to replace it with.
Let's make it happen! Open Panekill.exe in your favorite hex editor, and find the code
we want to replace. There are two ways to do this:
- Use the 'search' option in your hex editor, and search for the bytes representing the
original code. Make sure you use a long enough search string, so you won't replace the
wrong part of the program. To make sure there's only one match to your search string, just use
'search again' to see if it finds another hit. If so, make the search string longer.
- In WDasm, double-click on the first instruction of the code you want to replace, so that
it is highlighted. Now look at the status bar. It gives you the 'Offset' into the code. Just go
to there, in your hexeditor. You should see the code you were looking for.
Replace both parts, and save the file. MAKE SURE YOU HAVE A BACKUP!!
Now start up the program, and try to register it. Enter any information you want, and enter
a wrong serial number. Press 'OK', and you should see a messagebox with the correct registration
code! Pretty kewl, eh?
SAVE YOUR NEW, PATCHED COPY TOO BEFORE PROCEEDING!
Everything works now (if it all went ok), however there are still a few improvements that can
be made. For instance, when we look at the code at 00402E51:
mov dword ptr [ebp-08], eax
mov eax, dword ptr [ebp-08]
you will (hopefully) realise the second command is a total waste of time and space. (note that
the first one is not, because it stores the value to memory for use later on)
so, we can just NOP out the command at 00402E54 by replacing
8B45F8
with
909090
(This change is really pretty much useless, but i cannot help but obsess about these things :)
Also, the MessageBox we get, looks pretty lame. It just has the right registration number in it.
Nothing more, nothing less. In our hexeditor, we could replace
The registration code you have entered is incorrect. Please try again.
with something like
[Kwazy Webbit]
Your correct code is:
(To go to the next line, use a CR/LF pair. The code is 0A0D)
If we change nothing else, the registration code will now simply written over our text,
and it will still be the only thing in the messagebox. To change that, we should make the
wsprintfA write the code to a different place: the end of our text.
To do that, all we have to do is change the last parameter that gets PUSH-ed to wsprintfA
(lpOut) by adding our textsize to it. So in my case, I'd change:
push 00402194
to
push 0040211B
That's all there is to it for reversing the DREAD way.
This time.... MUAHHAHAhahahhahhhh
Kwazy Webbit style
This is more like a variation on 'The DREAD way'. While I was fiddling around with PaneKiller,
writing the previous chapter, I noticed the 'The DREAD way', although coming close, still
wasn't perfect. When you entered a wrong s/n the messagebox popped up, and gave you your
correct serial. You might think that's as good as a keygen can get, right? Wrong. There's one
more improvement that can be made to make it perfect. See, now you still have to look in the
messagebox, and remember your code (or write it down). What if we could make the program
enter the correct serial itself? It CAN be done.
I'm assuming you have made all the changes mentioned in the previous chapter (WITHOUT the extra
improvements!). Now, the program shows us a messagebox with the serial number ONLY.
We've already seen that the program uses GetDlgItemTextA to get our info out of the textboxes.
There is another API function to put info into those boxes: SetDlgItemTextA.
Let's compare GetDlgItemTextA with SetDlgItemTextA now:
GetDlgItemTextA: | | SetDlgItemTextA: |
hDlg | | hDlg |
nIDDlgItem | | nIDDlgItem |
lpString | | lpString |
nMaxCount | | |
(For a detailed explanation of all of these items, look in you programmer's reference, or
further up in this essay)
Knowing that we want to put the valid serial number into the same textfield, we notice two
things:
- The 'hDlg' should the same for both calls, since it is the same dialog box.
- The 'nIDDlgItem' should be the same for both calls, since it is the same textfield.
To find the needed values, we look at the call of the program to get our reg code.
We'll find (in WDasm) it uses
:00403063 8B4D08 mov ecx, dword ptr [ebp+08]
:00403066 51 push ecx
to PUSH the hDlg, and
:0040305E 68FE030000 push 000003FE
to PUSH the nIDDlgItem, so we will use the same in our call to SetDlgItemTextA.
We just need to know the lpString now. That's easy, it's the address wsprintfA uses to
print our registration code to:
push 00402194
(If you have made the 'improvements' to the messagebox, you may need to adjust this value)
So, the complete code for putting the serial in the appropriate textfield should be:
push 00402194
push 000003FE
mov ecx, dword ptr [ebp+08]
push ecx
call SetDlgItemTextA
We're going to be facing the same problem as we did in the previous chapter: we don't have any
space to put it in. The way the program is now, we see the messagebox with our code, which we
won't be needing anymore after we change it. We can just rip that part of the program out, and
replace it with our own code. the total call (PUSH-ing parameters and the CALL itself)to the
messagebox is:
:00403102 6A40 push 00000040
:00403104 8B0D1C2B4200 mov ecx, dword ptr [00422B1C]
:0040310A 51 push ecx
:0040310B 6894214200 push 00422194
:00403110 8B5508 mov edx, dword ptr [ebp+08]
:00403113 52 push edx
:00403114 FF158CE24100 call MessageBoxA
:0040311A ....
(That's a total of 0040311A-00403102=18h or 24 bytes)
To find out the hexadecimal representation for our code, use the same process as described in
the previous chapter. You should end up with something like:
6894214200 ( = push 00422194 )
68FE030000 ( = push 000003FE )
8B4D08 ( = mov ecx, dword ptr [ebp+08] )
51 ( = push ecx )
FF152CE34100 ( = call dword ptr [0041E32C] (call SetDlgItemTextA) )
which is a total of 20 bytes. We have to add 4 NOP commands then:
6894214200 68FE030000 8B4D08 51 FF152CE34100 90 90 90 90
Just use your favorite hexeditor (like previous chapter) to change the original
'MessageBoxA-code' into our own 'SetDlgItemTextA-code'. Start up PaneKiller, and try to register
with your own name and a fake reg number. When you press the 'OK' button, it should now
automatically replace your fake s/n with the real one!! Just press 'OK' again, and it is
should be registered to you.
You have just learned to 'crack like Kwazy'. Try something similar with other programs,
experiment as much as possible. (remember: always have backups!)
Have fun!
-NOTE- If your copy of PaneKiller is already registered:
start up 'c:\windows\regedit.exe' (just 'regedit' will do), and go to
HKEY_LOCAL_MACHINE/SOFTWARE/MaDdoG/PaneKiller. Delete 'Registered User', 'Registered User
Company' and 'Registration code'. You now have the shareware version again.
FINAL WORDS
I'd rate this protection at just below average, because I doubt a total newbie would crack this
one on his own, but just about every average or advanced cracker will not have too much
difficulty. The reason I picked this protection, is that it's relatively straightforward,
and it shows several interesting aspects of cracking, like reversing the XOR instruction.
Also, I later found out this target had real potential for cracking it 'The DREAD way', as well
as 'Kwazy Webbit style' (for more info, read the appropriate chapters).
Well, this was my first essay, hope you learned something. I know I have. There will probably
be more coming, but not right now. This one took me quite some time, and summer's coming!
Greetz to: all members of DREAD, koolzio (where the hell did you go?), all the other people
I know on IRC.
Also greetings to: ppl like Sandhopes, Hyrax, Dosy, Jo, Gelf and all others I see IRL.
Special thanx: Iczelion and Hutch, for teaching me win32 assembly
Wishing all of you a DREADful day ;-),
Kwazy Webbit of DREAD
"The only thing that keeps me sane, is my collection of singing potatoes..."
Look for me on IRC. I'm usually on EFnet in one or more of the channels:
#win32asm, #DREAD, #cracking4newbies and #cracking
CU there!
©1999 Kwazy Webbit productions